package org.hilo.boot.core.exception;

import org.hilo.boot.core.RefMsg;
import org.hilo.boot.core.UT;
import org.jretty.apibase.FixMsg;
import org.jretty.apibase.IMsg;
import org.jretty.apibase.MsgBase;
import org.jretty.apibase.MsgCodeHolder;
import org.jretty.util.AlgorithmUtils;
import org.jretty.util.BasicRuntimeException;
import org.jretty.util.DateFormatUtils;
import org.jretty.util.I18nExceptionSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Locale;
import java.util.TreeMap;

/**
 * 项目统一的异常
 * <p>
 * errorCode+errorMsg 格式，支持多国语言（zh_CN，zh_TW，en_US）
 * 
 * @see MsgBase
 * 
 * @author zollty
 */
public class AppException extends BasicRuntimeException implements I18nExceptionSupport, IMsg {
    private static final long serialVersionUID = -856748734106714570L;
    private static final Logger LOG = LoggerFactory.getLogger(AppException.class);
    private static final String THIS_NAME = AppException.class.getSimpleName();

    private String code;
    private IMsg msg;
    private final Object[] params;

    static {
        MsgCodeHolder.getMsgsMap().put(THIS_NAME, new TreeMap<>());
    }

    /**
     * 构造函数
     **/
    public AppException() {
        super();
        this.setMsg(null);
        this.params = null;
        this.keepStack = true;
    }

    /**
     * 构造函数
     **/
    public AppException(IMsg msg) {
        super();
        this.setMsg(msg);
        this.params = null;
    }

    /**
     * 构造函数
     **/
    public AppException(String message, Object... params) {
        super();
        this.params = null;
        if (message != null) {
            this.setMsg(new FixMsg(newCode(message), UT.Str.replaceParams(message, params)));
        } else {
            this.setMsg(null);
        }
        this.keepStack = true;
    }

    /**
     * 构造函数
     * 
     * @param params 占位符参数--[ 变长参数，用于替换message字符串里面的占位符"{}" ]
     */
    public AppException(IMsg msg, Object... params) {
        super();
        this.setMsg(msg);
        this.params = params;
    }

    /**
     * 记录异常信息，该方法会改变返回码code，让code和日志记录中的code一致。
     * 例如，文件上传失败提示用户： 文件上传失败，错误代码：7e5ab1
        try {
           ......
        } catch (Exception e) {
           throw new AppException("文件上传失败").logError(e);
        }
     * 那么可以凭 错误代码，在日志中快速找到错误信息。
     */
    public AppException logError(Throwable e) {
        this.setMsg(new RefMsg(this.msg, this.code + "-" + getUniqueCode()));
        LOG.error(this.msg.getCode() + ": " + this.msg.getMessage(), e);
        this.keepStack = false;
        return this;
    }

    private boolean keepStack = false;

    @Override
    public void printStackTrace() {
        if(keepStack) {
            super.printStackTrace();
        }
    }

    @Override
    public void printStackTrace(PrintWriter err) {
        if(keepStack) {
            super.printStackTrace(err);
        }
    }

    @Override
    public void printStackTrace(PrintStream err) {
        if(keepStack) {
            super.printStackTrace(err);
        }
    }

    @Override
    public StackTraceElement[] getStackTrace() {
        if(keepStack) {
            return super.getStackTrace();
        }
        return new StackTraceElement[0];
    }

    private void setMsg(IMsg msg) {
        this.msg = msg != null ? msg : MsgBase.OMIT_SYSTEM_ERR;
        this.code = this.msg.getCode();
    }

    /**
     * code的特点： 可以根据code找到对应的错误信息，且 code 和错误信息是一一对应关系。
     */
    public static String newCode(String errorMsg) {
        if (errorMsg != null) {
            // shorMsg算法
            String code = AlgorithmUtils.shortMsg(errorMsg);
            MsgCodeHolder.getMsgsMap().get(THIS_NAME).put(code, errorMsg);
            return code;
        }
        // 自动生成code，算法参见：initCode
        return initCode();
    }

    /**
     * 初始化错误代码，为它加上时间截，这样每个错误信息就是全局唯一的 <br>
     * 例如  imYn2e-051310381，错误码为 imYn2，时间戳为 051310381
     * <p>
     * 1、可以根据该code得知错误发生的服务器； 2、可以定位得知错误发生的时间（ddHHmmssS格式）。
     */
    private static String initCode() {
        return UT.Algr.shortMsg(getServerId()) + "-" + getUniqueCode();
    }

    /** 获取ddHHmmssXXX格式的日期，后3位是随机数 */
    public static String getUniqueCode() {
        DateFormatUtils.DateInfo dinfo = UT.Date.format();
        StringBuilder sbu = new StringBuilder(9);
        sbu.append(dinfo.getDd());
        sbu.append(dinfo.getHH());
        sbu.append(dinfo.getMm());
        sbu.append(dinfo.getSs());
        sbu.append(UT.Random.getRadomStr09AZaz(3));
        return sbu.toString();
    }

    private static String getServerId() {
        return UT.IP.getCachedHostName() + "$" + UT.IP.getCachedLocalIP();
    }

    /**
     * 等价于getMessage
     */
    @Override
    public String toString() {
        return toString(null);
    }

    @Override
    public String toString(String locale) {
        if(keepStack) {
            return code + ": " + getMessage(locale);
        }
        return getMessage(locale);
    }

    @Override
    public String getMessage() {
        return getMessage((String) null);
    }

    public String getMessage(Locale locale) {
        String localeStr = locale == null ? null : locale.toString();
        return getMessage(localeStr);
    }

    @Override
    public String getMessage(String locale) {
        if(keepStack) {
            return UT.Str.replaceParams(msg.getMessage(locale), params);
        }
        return code + ": " + UT.Str.replaceParams(msg.getMessage(locale), params);
    }

    @Override
    public String getErrorCode() {
        return code;
    }

    @Override
    public Object[] getParams() {
        if (params != null && params.length > 0) {
            return params.clone();
        }
        return params;
    }

    @Override
    public String getCode() {
        return code;
    }

}
